#include "GigInt_p.cpp"
#include <openssl/sha.h>
#include <cstdlib>
namespace sig
{
	template <typename Ty>
	class sig_token
	{
		public: 
			GigInt_p<Ty> r;
			GigInt_p<Ty> s;
		public:
			//constructors
			sig_token() {}

			sig_token(const GigInt_p<Ty> r, const GigInt_p<Ty> s):
					r(r), s(s) {}
			
			sig_token &operator=(const sig_token &that)
			{
				this->r = that.r;
				this->s = that.s;
				return *this;
			}
	};

#ifdef DEBUG	
	template <typename Ty>
	static bool verifyPoint(const GigInt_p<Ty> &x, const GigInt_p<Ty> &y)
	{
		string a_s = "3";
		string b_s = "64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1";
		GigInt_p<Ty> zero(utl::zero, utl::p_s);
		if(x == zero && y == zero)
			return true;
		//GigInt_p<Ty> oldA(a_s, utl::p_s);
		//GigInt_p<Ty>a = zero - oldA;
		GigInt_p<Ty> b_p(b_s, utl::p_s);
		//GigInt_p<Ty> result_y = y * y;
		//GigInt_p<Ty> result_x = (x * x * x) + (a * x) + b;
		//if(result_y == result_x)
		ZZ mod = GigInt<Ty>::GtoZZ(zero.getP());
		ZZ_p::init(mod);
		ZZ_p a = to_ZZ_p(0 - 3);
		ZZ_p b = to_ZZ_p(GigInt<Ty>::GtoZZ(b_p.getNum()));
		ZZ_p y_z = to_ZZ_p(GigInt<Ty>::GtoZZ(y.getNum()));
		ZZ_p x_z = to_ZZ_p(GigInt<Ty>::GtoZZ(x.getNum()));
		ZZ_p result_y = y_z * y_z;
		ZZ_p result_x = (x_z * x_z * x_z) + (a * x_z) + b;
		if(result_y == result_x)
			return true;
		return false;
	}

	template <typename Ty>
	static bool verifyModulusMult (const GigInt_p<Ty> p1,
											 const GigInt_p<Ty> p2)
	{
		GigInt<Ty> mod_corr(utl::p_s);
		if(mod_corr == p1.getP() && 
			mod_corr == p2.getP())
			return true;
		return false;
	}

	template <typename Ty> 
	static bool verifyModulusAdd (const GigInt_p<Ty> p1,
											const GigInt_p<Ty> p2, 
											const GigInt_p<Ty> p3, 
											const GigInt_p<Ty> p4)
	{
		GigInt<Ty> mod_corr(utl::p_s);
		if(mod_corr == p1.getP() && mod_corr == p2.getP() && 
				mod_corr == p3.getP() && mod_corr == p4.getP())
			return true;
		return false;
	}
#else
	template <typename Ty>
	static bool verifyPoint(const GigInt_p<Ty> &x, const GigInt_p<Ty> &y) { return true; }

	template <typename Ty>
	static bool verifyModulusMult (const GigInt_p<Ty> p1,
											 const GigInt_p<Ty> p2)
	{ return true; }

	template <typename Ty> 
	static bool verifyModulusAdd (const GigInt_p<Ty> p1,
											const GigInt_p<Ty> p2, 
											const GigInt_p<Ty> p3, 
											const GigInt_p<Ty> p4)
	{ return true; }
#endif	
	
	template <typename Ty>
	static void pointDouble(GigInt_p<Ty> *x, GigInt_p<Ty> *y)
	{
#ifdef DEBUG		
		if(!verifyModulusMult(*x, *y))
		{
			cerr << "PointMult: Incorrect modulus" << endl;
			throw fatal;
		}
		if(!verifyPoint(*x, *y))
		{
			cerr << "PointMult: input over the curve" << endl;
			cerr << "x "; x->getNum().dump();
			cerr << "y "; y->getNum().dump();
			throw fatal;
		}
#endif
		GigInt<Ty> zero(utl::zero);
		string s_a = "3"; //The real value is -3
		GigInt_p<Ty> oldX(*x);
		GigInt_p<Ty> oldY(*y);
		if(oldY == zero)
		{
			x->setNum(zero);
			y->setNum(zero);
			return;
		}
		GigInt_p<Ty> a(s_a, x->getP());
		GigInt_p<Ty> zero_p(zero, x->getP());
		a = zero_p - a;
		GigInt_p<Ty> oldX_sq(utl::zero, x->getP());
		oldX_sq = oldX * oldX;
		GigInt_p<Ty> L(utl::zero, utl::p_s);
  		L = ((oldX_sq + oldX_sq + oldX_sq + a)/(oldY + oldY));
		GigInt_p<Ty> newX(utl::zero, utl::p_s), newY(utl::zero, utl::p_s);
		newX = L * L - (oldX + oldX);
		newY = L * (oldX - newX) - oldY;	
		x->setNum(newX.getNum());
		y->setNum(newY.getNum());
#ifdef DEBUG
		if(!verifyPoint(*x, *y))
		{
			cerr << "PointMul: results outside the curve" << endl;
			cerr << "x "; x->getNum().dump();
			cerr << "y "; y->getNum().dump();
			cerr << "oldx "; oldX.getNum().dump();
			cerr << "oldy "; oldY.getNum().dump();
			ZZ mod = GigInt<Ty>::GtoZZ(oldX.getP());
			ZZ_p::init(mod);
			ZZ_p x_z = to_ZZ_p(GigInt<Ty>::GtoZZ(oldX.getNum()));
			ZZ_p x_sq_z = x_z * x_z;
			cerr << "oldX_sq " << x_sq_z << endl;
			cerr << "oldX_sq "; oldX_sq.getNum().dump();
			ZZ_p a_z = to_ZZ_p(GigInt<Ty>::GtoZZ(a.getNum()));
			cerr << "a + 3 " << (a_z + 3) << endl;
			ZZ_p y_z = to_ZZ_p(GigInt<Ty>::GtoZZ(oldY.getNum()));
			ZZ_p L_z = ((x_sq_z + x_sq_z + x_sq_z) + a_z)/(y_z + y_z);
			cerr << "L " << L_z << endl;
			cerr << "L "; L.getNum().dump();
			ZZ_p newX_z = L_z * L_z - (x_z + x_z);
			cerr << "newX " << newX_z << endl;
			cerr << "newX "; newX.getNum().dump();
			ZZ_p newY_z = L_z * (x_z - newX_z) - y_z;
			cerr << "newY " <<  (x_z - newX_z) << endl;
			cerr << "newY "; (oldX - newX).getNum().dump();
			cerr << "oldX "; oldX.getNum().dumpDebug();
			cerr << "newX "; newX.getNum().dumpDebug();
			for(int i = 0; i < newX.getNum().size(); ++i)
				cerr << (oldX.getNum().chunks[i] - newX.getNum().chunks[i]) << endl; 
			throw fatal;
		}
#endif
	}

	template <typename Ty>
	static void pointAdd(GigInt_p<Ty> *x1, GigInt_p<Ty> *y1, 
			const GigInt_p<Ty> &x2, const GigInt_p<Ty> &y2)
	{
		if(!verifyModulusAdd(*x1, *y1, x2, y2))
		{
			cerr << "PointAdd: incorrect modulus" << endl;
			cerr << "cor " << utl::p_s << endl;
			cerr << "x1  "; x1->getP().dump();
			cerr << "y1  "; y1->getP().dump();
			cerr << "x2  "; x2.getP().dump();
			cerr << "y2  "; y2.getP().dump();
			throw fatal;
		}
		if(!verifyPoint(*x1, *y1))
		{
			cerr << "PointAdd: point P1 incorrect" << endl;
			cerr << "x1 "; x1->getNum().dumpDebug();
			cerr << "y1 "; y1->getNum().dumpDebug();
			throw fatal;
		}
		if(!verifyPoint(x2, y2))
		{
			cerr << "PointAdd: point P2 incorrect" << endl;
			cerr << "x2 "; x2.getNum().dump();
			cerr << "y2 "; y2.getNum().dump();
			throw fatal;
		}
		GigInt<Ty>zero(utl::zero);
		if((x1->getNum() == zero) && (y1->getNum() == zero))
		{
			*x1 = x2;
			*y1 = y2;
			return;
		}
		if(x2.getNum() == zero && y2.getNum() == zero)
			return;
		if(x1->getNum() == x2.getNum())
		{
			if(y1->getNum() == y2.getNum())
			{
				pointDouble(x1, y1);
				return;
			}
			GigInt_p<Ty> p_zero(zero, y2.getP());
			GigInt_p<Ty> neg_y2;
		  	neg_y2 = p_zero - y2;
			if(y1->getNum() == neg_y2.getNum())
			{
				x1->setNum(zero);
				y1->setNum(zero);
				return;
			}
		}
		GigInt_p<Ty> oldX2(x2);
		GigInt_p<Ty> oldY2(y2);
		GigInt_p<Ty> oldX1(*x1);
		GigInt_p<Ty> oldY1(*y1);
		GigInt_p<Ty> L(utl::zero, x1->getP());
		L = (oldY1-oldY2)/(oldX1-oldX2);
		*x1 = (L * L) - oldX1 - oldX2;
		*y1 = (L * (oldX1 - (*x1))) - oldY1;
		if(!verifyPoint<Ty>(*x1, *y1))
		{
			cerr << "PointAdd: result outside the curve" << endl;
			ZZ mod = GigInt<Ty>::GtoZZ(oldX1.getP());
			ZZ_p::init(mod);
			ZZ_p oldX1_z = to_ZZ_p(GigInt<Ty>::GtoZZ(oldX1.getNum()));
			ZZ_p oldY1_z = to_ZZ_p(GigInt<Ty>::GtoZZ(oldY1.getNum()));
			ZZ_p oldX2_z = to_ZZ_p(GigInt<Ty>::GtoZZ(oldX2.getNum()));
			ZZ_p oldY2_z = to_ZZ_p(GigInt<Ty>::GtoZZ(oldY2.getNum()));
			ZZ_p L_z = (oldY1_z - oldY2_z) / (oldX1_z - oldX2_z);
			cerr << "L " << L_z << endl;
			cerr << "L "; L.getNum().dump();
			ZZ_p newX1_z = (L_z * L_z) - oldX1_z - oldX2_z;
		   cerr << "x1 " << newX1_z << endl;
			cerr << "x1 "; x1->getNum().dump();
			ZZ_p newY1_z = L_z * (oldX1_z - newX1_z) - oldY1_z;
			cerr << "y1 " << newY1_z << endl;
			cerr << "y1 "; y1->getNum().dump();	
			
			GigInt_p<Ty> newX1(GigInt<Ty>(rep(newX1_z)), GigInt<Ty>(mod));
			GigInt_p<Ty> newY1(GigInt<Ty>(rep(newY1_z)), GigInt<Ty>(mod));
			if(verifyPoint<Ty>(newX1, newY1))
			{
				cerr << "something wrong in verifyPoints" << endl;
			}
			else
			{
				cerr << "something wrong with the math" << endl;
			}


		}	
	}
	


	template <typename Ty>
	static void scalar_mul(const GigInt<Ty> k, const GigInt_p<Ty> &Gx, 
			const GigInt_p<Ty> &Gy,GigInt_p<Ty> *Rx, GigInt_p<Ty> *Ry)
	{
		GigInt_p<Ty> Qx(utl::zero, Gx.getP());
		GigInt_p<Ty> Qy(utl::zero, Gx.getP());
		int chunk = 0, pos = 0;	
		bool flag = false;
		for(int i=k.bitSize() - 1;i>=0;--i)
		{
			if(flag)
			{	
				pointDouble(&Qx,&Qy);
			}
			chunk = i / (sizeof(Ty) * 8);
			pos = i % (sizeof(Ty) * 8);
			if((k.chunks[chunk]) & (((Ty)1) << pos))
			{
				flag = true;
				pointAdd(&Qx,&Qy, Gx, Gy);
			}
		}
		*Rx = Qx;
		*Ry = Qy;
		return;
	}

	template <typename Ty>
	static GigInt_p<Ty> scalar_mul_x(GigInt<Ty> k, GigInt_p<Ty> Gx, 
			GigInt_p<Ty> Gy)
	{	
		GigInt_p<Ty> Qx(utl::zero, Gx.getP());
		GigInt_p<Ty> Qy(utl::zero, Gy.getP());
		scalar_mul<Ty>(k, Gx, Gy, &Qx, &Qy);
		return Qx;
	}



	template <typename Ty>
	static void scalar_mul_test(GigInt<Ty> k, GigInt_p<Ty> &Gx,
			GigInt_p<Ty> &Gy, GigInt_p<Ty> *Rx, GigInt_p<Ty> *Ry)
	{
		GigInt_p<Ty> Qx(utl::zero,Gx.getP());
		GigInt_p<Ty> Qy(utl::zero,Gx.getP());
		GigInt<Ty> i(utl::zero);
		for(; i != k; ++i)
		{
			if(Qx == (*Rx) && Qy == (*Ry))
			{
				cerr << "-------Equal at "; i.dump(); 
			}
			pointAdd(&Qx, &Qy, Gx, Gy);
			if(!sig::verifyPoint<Ty>(Qx, Qy))
			{
				cerr << "----Scalar_mul_test: Points over the curve ------" << endl;
				cerr << "Qx "; Qx.dump();
				cerr << "Qy "; Qy.dump();
				cerr << "i  "; i.dump();
			}
		}
		if(!sig::verifyPoint<Ty>(Qx, Qy))
			cerr << "------Scalar_mul_test: Points over the curve ------" << endl;
		if(Qx == (*Rx) && Qy == (*Ry))
		{
			cerr << "-------Equal at "; i.dump(); 
		}
		*Rx = Qx;
		*Ry = Qy;
		return;
	}
	

	template <typename Ty>
	static bool pointsEqual(GigInt_p<Ty> &p1x, GigInt_p<Ty> &p1y, 
			GigInt_p<Ty> &p2x, GigInt_p<Ty> &p2y)
	{
		return ((p1x == p2x) && (p1y == p2y));	
	}

	template <typename Ty>
	static GigInt<Ty>  hash(GigInt<Ty> &m)
	{
		unsigned char result[20] = "0";
		string input = "abc"; //m.to_str();
		SHA1((const unsigned char *)input.c_str(), input.size(), result);
		string res;
		int i = 0;
		for(; i < 20; ++i)
		{
			char c[2], d[2];
			unsigned char x = result[i];
			sprintf(c, "%x", x >> 4);
			sprintf(d, "%x", x & 0xf);
			res.push_back(c[0]);
			res.push_back(d[0]);
		}
		return GigInt<Ty>(res);
	}

	template <typename Ty>
	static bool sig_verification(sig_token<Ty> &token, GigInt<Ty> &d, 
			GigInt<Ty> &m)
	{
		GigInt<Ty> h = hash(m);
		GigInt<Ty> n(utl::n_s);
		GigInt_p<Ty> u1(((token.s.inv()) * h).getNum() , utl::n_s);	
		GigInt_p<Ty> u2((token.r * token.s.inv()).getNum(), utl::n_s);
		GigInt_p<Ty> tmp_Gx(utl::gx_s,utl::p_s);
		GigInt_p<Ty> tmp_Gy(utl::gy_s,utl::p_s);
		GigInt_p<Ty> r1_x(utl::zero,utl::p_s), r1_y(utl::zero,utl::p_s), 
			r2_x(utl::zero,utl::p_s), r2_y(utl::zero,utl::p_s);
		scalar_mul<Ty>(u1.getNum(), tmp_Gx, tmp_Gy, &r1_x, &r1_y);
		scalar_mul<Ty>(d, tmp_Gx, tmp_Gy, &r2_x,&r2_y);
		scalar_mul<Ty>(u2.getNum(), r2_x, r2_y, &r2_x,&r2_y);

		pointAdd(&r1_x,&r1_y,r2_x,r2_y);
		GigInt_p<Ty> v(r1_x.getNum(),utl::n_s);

		if(v == token.r)
				return true;
		else return false;
	}	
	
	template <typename Ty>
	static  sig_token<Ty> signature_gen(GigInt<Ty> m, GigInt<Ty> d)
	{
		GigInt_p<Ty> Gx(utl::gx_s, utl::p_s);
		GigInt_p<Ty> Gy(utl::gy_s, utl::p_s);
		GigInt<Ty> zero(utl::zero);
		GigInt_p<Ty> r(utl::zero, utl::n_s);
		GigInt_p<Ty> s(utl::zero, utl::n_s);
		GigInt<Ty> e, temp;
		e = hash<Ty>(m);

		while(r == zero || s == zero)
		{
			temp.randGigInt("1",utl::n_s);
			GigInt_p<Ty> k(temp, utl::n_s);
			r = scalar_mul_x<Ty>(k.getNum(),Gx,Gy);
			s = ((r * d) + e) * k.inv();
		}
		return sig_token<Ty>(r,s);
	}


	template <typename Ty>
	static  sig_token<Ty> faulted_signature_gen(GigInt<Ty> &m, GigInt<Ty> &d, int ind)
	{
		string n_s = "6277101735386680763835789423176059013767194773182842284081";
		string p_s = "6277101735386680763835789423207666416083908700390324961279";
		string gx_s = "188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012";
		string gy_s = "07192b95ffc8da78631011ed6b24cdd573f977a11e794811";
		string zero_s = "0";
		string d_s = "";
		GigInt_p<Ty> Gx(gx_s, p_s);
		GigInt_p<Ty> Gy(gy_s, p_s);
		GigInt<Ty> zero(zero_s);
		GigInt_p<Ty> r(zero_s, n_s);
		GigInt_p<Ty> s(zero_s, n_s);
		GigInt<Ty> e, temp;
		e = hash<Ty>(m);
			
		while(r == zero || s == zero)
		{
			temp.randGigInt("1",n_s);
			GigInt_p<Ty> k(temp,n_s);
			r = scalar_mul_x<Ty>(k.getNum(),Gx,Gy);
			GigInt<Ty> mul = (GigInt<Ty>::schoolbookMulFaulted(r.getNum(),d,ind));
			mul.mod192();
			s = k.inv() * (e + mul);
		}
		return sig_token<Ty>(r,s);
	}

}//end of namespace sig
